#include <cstdarg>
#include <sstream>

#include "sgeString.h"

#include "object.h"
using namespace sge;

Object Object::root("root","root",0);

//tree

Object::Object(const String& _name, const String& _type, Object* _parent) :
    name(_name),
    type(_type),
    parent(_parent),
    initialized(false),
    active(true),
    destroyed(false),
    madeScriptServer(false) {
    
    fixPath();
    
    if (!parent) {
        scriptServer = new ScriptServer;
        madeScriptServer = true;
        initFileSearchPaths();
    }
    
}

Object::~Object() {
    destroy();
    
    if (madeScriptServer && scriptServer) {
        delete scriptServer;
        scriptServer = 0;
    }
    
    if (parent) {
        parent->children.erase(name);
        parent = 0;
    }
}

void Object::fixPath() {
    if (parent) {
        if (parent->parent) {
            path = parent->path + "/" + name;
        } else {
            path = "/" + name;
        }
    } else
        path = "/";
}

bool Object::removeChildNow(const String& childName) {
    if (hasChild(childName)) {
        Object* child = children[childName];
        children.erase(childName);
        delete child;
        return true;
    } else
        return false;
}

Object* Object::addChild(const String& childName, const String& childType) {
    if (!hasChild(childName) || removeChildNow(childName)) {
        Object* child = new Object(childName,childType,this);
        children[childName] = child;
        return child;
    } else
        return 0;
}

Object* Object::getChild(const String& childName) {
    if (hasChild(childName))
        return children[childName];
    else
        return 0;
}

bool Object::hasChild(const String& childName) const {
    return children.find(childName) != children.end();
}

StringObjectPtrMap& Object::getChildren() {
    return children;
}

StringList Object::getChildNames() {
    StringList names;
    for (StringObjectPtrMap::iterator i = children.begin(); i != children.end(); ++i)
        names.push_back(i->first);
    return names;
}

PythonObject Object::pyGetChildNames() {
    boost::python::list names;
    for (StringObjectPtrMap::iterator i = children.begin(); i != children.end(); ++i)
        names.append(i->first);
    return names;
}

void Object::removeChild(const String& childName) {
    childrenToRemove.push_back(childName);
}

bool Object::hasParent() const {
    return parent != 0;
}

Object* Object::getParent() const {
    return parent;
}

Object* Object::getRoot() {
    if (parent)
        return parent->getRoot();
    else
        return this;
}

Object* Object::getObject(const String& objectPath) {
    //log("getObject(%s)",objectPath.c_str());

    if (objectPath == "/") {
        //log("returning root");
        return getRoot();
    }

    if (objectPath[0] == '/') {
        //log("recursing to root");
        //static StringObjectMap cache;
/*        if (Object::cache.find(objectPath) == cache.end()) {
            Object* obj = getRoot()->getObject(objectPath.substr(1));
            Object::cache[objectPath] = obj;
            return obj;
        } else
            return Object::cache[objectPath];*/
        return getRoot()->getObject(objectPath.substr(1));
    }

    int firstSlash = objectPath.find("/");
    if (firstSlash == String::npos) {
        //log("path is simple");
        if (objectPath == "..") {
            //log("returning parent");
            return getParent();
        } else if (hasChild(objectPath)) {
            //log("returning child");
            return getChild(objectPath);
        } else {
            //log("returning 0");
            return 0;
        }
    } else {
        //log("path is complex");
        String childName = objectPath.substr(0,firstSlash),
            restPath = objectPath.substr(firstSlash+1);

        if (childName == "..") {
            if (parent) {
                //log("recursing to parent");
                return getParent()->getObject(restPath);
            } else {
                //log("no parent to recurse to");
                return 0;
            }
        } else {
            if (hasChild(childName)) {
                //log("recursing to child %s",childName.c_str());
                return getChild(childName)->getObject(restPath);
            } else {
                //log("no child %s to recurse to",childName.c_str());
                return 0;
            }
        }
    }
}

Object* Object::getFirstObjectOfName(const String& objName, bool fromRoot) {
    if (fromRoot)
        return getRoot()->getFirstObjectOfName(objName,false);
    else if (name == objName)
        return this;
    else {
        Object* obj;
        for (StringObjectPtrMap::iterator i = children.begin(); i != children.end(); ++i) {
            obj = i->second->getFirstObjectOfName(objName,fromRoot);
            if (obj)
                return obj;
        }
        return 0;
    }
}

Object* Object::pyGetFirstObjectOfName1(const String& objName) {
    return getFirstObjectOfName(objName);
}

Object* Object::pyGetFirstObjectOfName2(const String& objName, bool fromRoot) {
    return getFirstObjectOfName(objName,fromRoot);
}

ObjectPtrList Object::getAllObjectsOfName(const String& objName, bool fromRoot) {
    if (fromRoot)
        return getRoot()->getAllObjectsOfName(objName,false);
    else {
        ObjectPtrList list;
        if (name == objName)
            list.push_back(this);
            
        for (StringObjectPtrMap::iterator i = children.begin(); i != children.end(); ++i) {
            ObjectPtrList childList = i->second->getAllObjectsOfName(objName,fromRoot);
            list.merge(childList);
        }
        
        return list;
    }
}

Object* Object::getFirstObjectOfType(const String& objType, bool fromRoot) {
    if (fromRoot)
        return getRoot()->getFirstObjectOfType(objType,false);
    else if (type == objType)
        return this;
    else {
        Object* obj;
        for (StringObjectPtrMap::iterator i = children.begin(); i != children.end(); ++i) {
            obj = i->second->getFirstObjectOfType(objType,fromRoot);
            if (obj)
                return obj;
        }
        return 0;
    }
}

Object* Object::pyGetFirstObjectOfType1(const String& objType) {
    return getFirstObjectOfType(objType);
}

Object* Object::pyGetFirstObjectOfType2(const String& objType, bool fromRoot) {
    return getFirstObjectOfType(objType,fromRoot);
}

ObjectPtrList Object::getAllObjectsOfType(const String& objType, bool fromRoot) {
    if (fromRoot)
        return getRoot()->getAllObjectsOfType(objType,false);
    else {
        ObjectPtrList list;
        if (type == objType)
            list.push_back(this);
            
        for (StringObjectPtrMap::iterator i = children.begin(); i != children.end(); ++i) {
            ObjectPtrList childList = i->second->getAllObjectsOfType(objType,fromRoot);
            list.merge(childList);
        }
        
        return list;
    }
}

//time

void Object::addInitDependency(const String& dependPath) {
    initDependencies.push_back(dependPath);
}

void Object::addDestroyDependency(const String& dependPath) {
    destroyDependencies.push_back(dependPath);
}

void Object::processInitDependencies() {
    Object* obj;
    for (StringList::iterator i = initDependencies.begin(); i != initDependencies.end(); ++i) {
        obj = getObject(*i);
        if (obj)
            obj->init();
    }
}

void Object::processDestroyDependencies() {
    Object* obj;
    for (StringList::iterator i = destroyDependencies.begin(); i != destroyDependencies.end(); ++i) {
        obj = getObject(*i);
        if (obj)
            obj->destroy();
    }
}

bool Object::init() {
    if (!initialized && !destroyed) {
        processInitDependencies();
        
        load(type + ".yml");
        
        log("init");
        
        for (StringAspectPtrMap::iterator i = aspects.begin(); i != aspects.end(); ++i) {
            log("init aspect %s",i->first.c_str());
            i->second->preInit();
        }
        
        for (StringObjectPtrMap::iterator i = children.begin(); i != children.end(); ++i)
            i->second->init();
            
        receiveMessage("init",VarDict());
        
        initialized = true;
        return true;
    }
    return false;
}

bool Object::update(float time, float dtime) {
    if (initialized && active && !destroyed) {
//         log("update");
        
        if (childrenToRemove.size()) {
            for (StringList::iterator i = childrenToRemove.begin(); i != childrenToRemove.end(); ++i)
                removeChildNow(*i);
            childrenToRemove.clear();
        }
        
        for (StringAspectPtrMap::iterator i = aspects.begin(); i != aspects.end(); ++i)
            i->second->preUpdate(time,dtime);
        
        for (StringObjectPtrMap::iterator i = children.begin(); i != children.end(); ++i) {
            if (initialized && !destroyed && !i->second->initialized)
                i->second->init();
            i->second->update(time,dtime);
        }
        
        return true;
    }
    return false;
}

bool Object::destroy() {
    if (initialized && !destroyed) {
        processDestroyDependencies();
    
//         log("destroy");
        
        receiveMessage("destroy",VarDict());
        
        for (StringAspectPtrMap::iterator i = aspects.begin(); i != aspects.end(); ++i)
            i->second->preDestroy();
        
        for (StringObjectPtrMap::iterator i = children.begin(); i != children.end(); ++i) {
            delete i->second;
        }
        children.clear();
        
        destroyed = true;
        
        return true;
    }
    return false;
}

bool Object::getActive() const {
    return active;
}

void Object::setActive(bool _active) {
    active = _active;
}

//log

void Object::log(const char* format, ...) {
    char buffer[1024];
    va_list ap;
    va_start(ap,format);
    vsprintf(buffer,format,ap);
    
    printf("%s: %s\n",path.c_str(),buffer);
}

void Object::pyLog(const String& str) {
    log(str.c_str());
}

//aspects

bool Object::hasAspect(const String& aspectName) {
    return aspects.find(aspectName) != aspects.end();
}

Aspect* Object::getAspect(const String& aspectName) {
    if (hasAspect(aspectName))
        return aspects[aspectName];
    else
        return 0;
}

bool Object::removeAspect(const String& aspectName) {
    if (hasAspect(aspectName)) {
        Aspect* aspect = aspects[aspectName];
        aspect->preDestroy();
        delete aspect;
        return true;
    } else
        return false;
}

Aspect* Object::addAspect(const String& aspectName) {
    Aspect* aspect = aspectFactory(aspectName);
    if (aspect) {
        aspects[aspectName] = aspect;
        if (initialized && !destroyed)
            aspect->preInit();
    } else
        log("unknown aspect type %s",aspectName.c_str());
    return aspect;
}

void Object::pyAddAspect(const String& aspectName) {
    addAspect(aspectName);
}

//vars

void Object::onSetVar(const String& varName) {
    for (StringAspectPtrMap::iterator i = aspects.begin(); i != aspects.end(); ++i)
        i->second->onSetVar(varName);
}

void Object::onGetVar(const String& varName) {
    for (StringAspectPtrMap::iterator i = aspects.begin(); i != aspects.end(); ++i)
        i->second->onGetVar(varName);
}

Vector3 Object::makeVector(float x, float y, float z) {
    return Vector3(x,y,z);
}

Quaternion Object::makeQuaternion(Vector3 a, float r) {
    return Quaternion(r,a);
}

//files

void Object::initFileSearchPaths() {
    String filename = String(SGE_PROJECT) + "_paths.txt";
    FILE* fp = fopen(filename.c_str(),"r");
    char buffer[1024];
    while (!feof(fp)) {
        fscanf(fp,"%s",buffer);
        fileSearchPaths.push_back(String(buffer));
    }
    fclose(fp);
}

bool Object::findFile(const String& filename, String& result) {
    for (StringList::iterator i = fileSearchPaths.begin(); i != fileSearchPaths.end(); ++i) {
        String testFilename = *i + "/" + filename;
        FILE* fp = fopen(testFilename.c_str(),"r");
        if (fp) {
            fclose(fp);
            result = testFilename;
            return true;
        }
    }
    return false;
}

PythonObject Object::pyFindFile(const String& filename) {
    String absFilename;
    if (findFile(filename,absFilename))
        return PythonObject(absFilename);
    else
         return PythonObject();
}

bool Object::load(const String& filename) {
    String absFilename;
    if (findFile(filename, absFilename)) {
        log("load %s from %s",filename.c_str(),absFilename.c_str());
        
        PythonObject yaml = scriptServer->importModule("libs/yaml");
        PythonObject fp = scriptServer->getContext().attr("__builtins__").attr("open")(absFilename);
        PythonObject sgeLoad = yaml.attr("sgeLoad");
        PythonObject yamlDict = sgeLoad(fp);
        fp.attr("close")();
            
        loadObject(yamlDict);
    }
}

void Object::loadObject(PythonObject yamlDict) {
    PythonObject items = yamlDict.attr("items")();
    String key;
    int len = scriptServer->getLength(items), i;
    for (i = 0; i < len; ++i) {
        key = boost::python::extract<String>(items[i][0]);
        
        if (key == "vars")
            loadVars(items[i][1]);
        else if (key == "aspects")
            loadAspects(items[i][1]);
        else if (key == "children")
            loadChildren(items[i][1]);
        else if (key == "include")
            loadIncludes(items[i][1]);
        else if (key == "initDependencies")
            loadInitDependencies(items[i][1]);
        else if (key == "destroyDependencies")
            loadDestroyDependencies(items[i][1]);
        else if (key == "scriptFiles")
            loadScriptFiles(items[i][1]);
        else if (key == "scriptActions")
            0; //handled in second pass
        else if (key == "type")
            0; //handled in loadChildren
        else
            log("ERROR: unknown object tag %s",key.c_str());
        
    }

    for (i = 0; i < len; ++i) {
        key = boost::python::extract<String>(items[i][0]);
        if (key == "scriptActions") {
            loadScriptActions(items[i][1]);
        }
    }
}

void Object::loadVars(PythonObject yamlDict) {
    PythonObject items = yamlDict.attr("items")();
    String key;
    for (int i = 0; i < boost::python::extract<int>(items.attr("__len__")()); ++i) {
        key = boost::python::extract<String>(items[i][0]);
        pySetVar(key,items[i][1]);
    }
}

void Object::loadAspects(PythonObject yamlDict) {
    String key;
    for (int i = 0; i < boost::python::extract<int>(yamlDict.attr("__len__")()); ++i) {
        key = boost::python::extract<String>(yamlDict[i]);
        log("load aspect %s",key.c_str());
        addAspect(key);
    }
}

void Object::loadChildren(PythonObject yamlDict) {
    PythonObject items = yamlDict.attr("items")();
    String childName, childType;
    Object* child;
    for (int i = 0; i < boost::python::extract<int>(items.attr("__len__")()); ++i) {
        childName = boost::python::extract<String>(items[i][0]);
        boost::python::dict childDict = boost::python::extract<boost::python::dict>(items[i][1]);
        String childType = childName;
        if (childDict.has_key("type"))
            childType = boost::python::extract<String>(childDict["type"]);
        if (hasChild(childName)) 
            child = getChild(childName);
        else
            child = addChild(childName,childType);
        child->loadObject(childDict);
    }
}

void Object::loadIncludes(PythonObject yamlDict) {
    String dataType = boost::python::extract<String>(yamlDict.attr("__class__").attr("__name__"));
    if (dataType == "str") {
        String includeType = boost::python::extract<String>(yamlDict);
        if (load(includeType))
            includeList.push_back(includeType);
    } else if (dataType == "list") {
        String includeType;
        for (int i = 0; i < boost::python::extract<int>(yamlDict.attr("__len__")()); ++i) {
            includeType = boost::python::extract<String>(yamlDict[i]);
            if (load(includeType))
                includeList.push_back(includeType);
        }
    } else
        log("ERROR: include must be a string or list of strings");
}

void Object::loadInitDependencies(PythonObject yamlDict) {
    String objPath;
    for (int i = 0; i < boost::python::extract<int>(yamlDict.attr("__len__")()); ++i) {
        objPath = boost::python::extract<String>(yamlDict[i]);
        addInitDependency(objPath);
    }
}

void Object::loadDestroyDependencies(PythonObject yamlDict) {
    String objPath;
    for (int i = 0; i < boost::python::extract<int>(yamlDict.attr("__len__")()); ++i) {
        objPath = boost::python::extract<String>(yamlDict[i]);
        addDestroyDependency(objPath);
    }
}

void Object::loadScriptFiles(PythonObject yamlDict) {
    if (scriptServer->getType(yamlDict) == "str") {
        loadScriptFile(boost::python::extract<String>(yamlDict));
    } else {
        int len = scriptServer->getLength(yamlDict), i;
        for (i = 0; i < len; ++i)
            loadScriptFile(boost::python::extract<String>(yamlDict[i]));
    }
}

void Object::loadScriptActions(PythonObject yamlDict) {
    PythonObject items = yamlDict.attr("items")();
    int len = scriptServer->getLength(items), i;
    String cmd, actionName;
    for (i = 0; i < len; ++i) {
        cmd = boost::python::extract<String>(items[i][0]);
        actionName = boost::python::extract<String>(items[i][1]);
        addScriptAction(cmd,actionName);
    }
}

void Object::save(const String& filename, bool rec) {
    PythonObject fp = scriptServer->getContext().attr("__builtins__").attr("open")(filename,"w"),
        yamlDict = saveObject(rec);
    scriptServer->importModule("libs/yaml").attr("sgeDump")(yamlDict,fp);
    fp.attr("close")();
}

void Object::pySave1(const String& filename) {
    save(filename);
}

void Object::pySave2(const String& filename, bool rec) {
    save(filename,rec);
}

PythonObject Object::saveObject(bool rec) {
    boost::python::dict yamlDict;
    
    PythonObject varDict = toPythonDict();
    if (boost::python::extract<int>(varDict.attr("__len__")()))
        yamlDict["vars"] = varDict;
        
    PythonObject aspectDict = saveAspects();
    if (boost::python::extract<int>(aspectDict.attr("__len__")()))
        yamlDict["aspects"] = aspectDict;
        
    PythonObject includeDict = saveIncludes();
    if (boost::python::extract<int>(includeDict.attr("__len__")()))
        yamlDict["include"] = includeDict;
        
    PythonObject initDependencyList = saveInitDependencies();
    if (boost::python::extract<int>(initDependencyList.attr("__len__")()))
        yamlDict["initDependencies"] = initDependencyList;
        
    PythonObject destroyDependencyList = saveDestroyDependencies();
    if (boost::python::extract<int>(destroyDependencyList.attr("__len__")()))
        yamlDict["destroyDependencies"] = destroyDependencyList;
        
    if (name != type)
        yamlDict["type"] = type;
        
    if (rec) {
        PythonObject childrenDict = saveChildren();
        if (boost::python::extract<int>(childrenDict.attr("__len__")()))
            yamlDict["children"] = childrenDict;
    }
        
    return yamlDict;
}

PythonObject Object::saveAspects() {
    boost::python::list yamlDict;
    
    for (StringAspectPtrMap::iterator itr = aspects.begin(); itr != aspects.end(); ++itr)
        yamlDict.append(PythonObject(itr->first));
        
    return yamlDict;
}

PythonObject Object::saveIncludes() {
    boost::python::list yamlDict;
    
    for (StringList::iterator itr = includeList.begin(); itr != includeList.end(); ++itr)
        yamlDict.append(PythonObject(*itr));
        
    return yamlDict;
}

PythonObject Object::saveChildren() {
    boost::python::dict yamlDict;
    
    for (StringObjectPtrMap::iterator itr = children.begin(); itr != children.end(); ++itr)
        yamlDict[PythonObject(itr->first)] = itr->second->saveObject(true);
        
    return yamlDict;
}

PythonObject Object::saveInitDependencies() {
    boost::python::list yamlList;
    
    for (StringList::iterator itr = initDependencies.begin(); itr != initDependencies.end(); ++itr)
        yamlList.append(*itr);
        
    return yamlList;
}

PythonObject Object::saveDestroyDependencies() {
    boost::python::list yamlList;
    
    for (StringList::iterator itr = destroyDependencies.begin(); itr != destroyDependencies.end(); ++itr)
        yamlList.append(*itr);
        
    return yamlList;
}

//scripts

bool Object::hasScriptCmd(const String& cmd) {
    return scriptActions.find(cmd) != scriptActions.end();
}

bool Object::hasScriptAction(const String& cmd, const String& actionName) {
    if (hasScriptCmd(cmd))
        for (StringList::iterator i = scriptActions[cmd].begin(); i != scriptActions[cmd].end(); ++i)
            if (*i == actionName)
                return true;
    return false;
}

bool Object::addScriptAction(const String& cmd, const String& actionName) {
    if (scriptServer->hasFunction(actionName)) {
        if (!hasScriptCmd(cmd))
            scriptActions[cmd] = StringList();
        scriptActions[cmd].push_back(actionName);
        return true;
    } else {
        return false;
    }
}

bool Object::removeScriptAction(const String& cmd, const String& actionName) {
    if (hasScriptAction(cmd,actionName))
        scriptActions[cmd].remove(actionName);
    else
        return false;
}

bool Object::loadScriptFile(const String& filename) {
    String absFilename;
    if (findFile(filename,absFilename)) {
        log("load %s from %s",filename.c_str(),absFilename.c_str());
        scriptServer->loadFunctions(absFilename);
    }
}

//messages

//todo: reimplement this with smart pointers

bool Object::isSubscribed(const String& objPath) {
    for (StringList::iterator i = subscriberList.begin(); i != subscriberList.end(); ++i)
        if (*i == objPath)
            return true;
    return false;
}

bool Object::subscribe(const String& objPath) {
    if (!isSubscribed(objPath)) {
        subscriberList.push_back(objPath);
        return true;
    } else
        return false;
}

bool Object::unsubscribe(const String& objPath) {
    if (isSubscribed(objPath)) {
        subscriberList.remove(objPath);
        return true;
    } else
        return false;
}

void Object::publish(const String& cmd, const VarDict& args) {
    Object* obj;
    for (StringList::iterator i = subscriberList.begin(); i != subscriberList.end(); ++i) {
        obj = getObject(*i);
        if (obj)
            obj->receiveMessage(cmd,args);
    }
}

void Object::pyPublish(const String& cmd, boost::python::dict args) {
    publish(cmd,VarDict(args));
}

void Object::receiveMessage(const String& cmd, const VarDict& args) {
    for (StringAspectPtrMap::iterator i = aspects.begin(); i != aspects.end(); ++i)
        i->second->receiveMessage(cmd,args);
        
    if (hasScriptCmd(cmd))
        for (StringList::iterator i = scriptActions[cmd].begin(); i != scriptActions[cmd].end(); ++i) {
            PythonObject f = scriptServer->getFunction(*i);
            
            PythonObject obj(this);
            Py_INCREF(obj.ptr());
            f(obj,cmd,args.toPythonDict());
        }
}

typedef boost::python::return_value_policy<boost::python::reference_existing_object> returnPointer;

void Object::registerPythonBindings() {
    boost::python::class_<Object, boost::python::bases<VarDict> >("Object", boost::python::no_init)
        
        //tree
        .def_readonly("root",Object::root)
        
        .def("addChild",&Object::addChild, returnPointer())
        .def("getChild",&Object::getChild, returnPointer())
        .def("hasChild",&Object::hasChild)
        .def("getChildNames",&Object::pyGetChildNames)
        .def("removeChild",&Object::removeChild)
        
        .def("getObject",&Object::getObject, returnPointer())
        .def("hasParent",&Object::hasParent)
        .def("getParent",&Object::getParent, returnPointer())
        .def("getRoot",&Object::getRoot, returnPointer())
        
        .def("getFirstObjectOfName",&Object::pyGetFirstObjectOfName1, returnPointer())
        .def("getFirstObjectOfName",&Object::pyGetFirstObjectOfName2, returnPointer())
        .def("getFirstObjectOfType",&Object::pyGetFirstObjectOfType1, returnPointer())
        .def("getFirstObjectOfType",&Object::pyGetFirstObjectOfType2, returnPointer())
        
        .def("getName",&Object::getName)
        .def("getType",&Object::getType)
        .def("getPath",&Object::getPath)
        
        //time
        
        .def("init",&Object::init)
        .def("update",&Object::update)
        .def("destroy",&Object::destroy)
        .def("getActive",&Object::getActive)
        .def("setActive",&Object::setActive)
        .def("addInitDependency",&Object::addInitDependency)
        .def("addDestroyDependency",&Object::addDestroyDependency)
        
        //log
        
        .def("log",&Object::pyLog)
        
        //aspects
        
        .def("hasAspect",&Object::hasAspect)
        .def("removeAspect",&Object::removeAspect)
        .def("addAspect",&Object::pyAddAspect)
        
        //files
        
        .def("save",&Object::pySave1)
        .def("save",&Object::pySave2)
        .def("load",&Object::load)
        .def("findFile",&Object::pyFindFile)
        
        //scripts
        
        .def("loadScriptFile",&Object::loadScriptFile)
        .def("hasScriptAction",&Object::hasScriptAction)
        .def("addScriptAction",&Object::addScriptAction)
        .def("removeScriptAction",&Object::removeScriptAction)
        
        //messages
        
        .def("subscribe",&Object::subscribe)
        .def("isSubscribed",&Object::isSubscribed)
        .def("unsubscribe",&Object::unsubscribe)
        .def("publish",&Object::pyPublish)
        
        //vars
        
        .def("makeVector",&Object::makeVector)
        .def("makeQuaternion",&Object::makeQuaternion)
    ;
}
